from endBreakpoint import EndBreakpoint
from functionNameRegistry import FunctionNameRegistry
import jeringa
import rtems
import itertools
import re
import gdb

# Drives the debugging target by executing continues, stepis, and so on.
# Needs an OS-dependant RunInfoManager to get info on things like init functions
# and an SCC mechanism-dependant to get info on Self-Correction Code.
# USED BY: RunManager.
# USES: RTEMSRunInfoManager, FunctionNameRegistry.

class TargetDriver(object):
    def __init__(self, SCCDetector, functionsToSkip):
        self.SCCDetector = SCCDetector
        self.functionNameRegistry = FunctionNameRegistry()

        if functionsToSkip:
            self.functionNameRegistry.registerFunctions(functionsToSkip)

            if "names" in functionsToSkip:
                for functionName in functionsToSkip["names"]:
                    gdb.execute("skip %s" % functionName)

    def advanceTo(self, location):
        gdb.execute("advance %s" % location)

    def mustSkipFunction(self, functionName):
        return self.functionNameRegistry.isRegistered(functionName)

################################################################################

# A TargetDriver used for performing Golden Runs. Logs all the values the
# Program Counter (PC) has from the init function's first instruction until it
# hits an EndBreakpoint set by a GoldenRunManager.
# USED BY: GoldenRunManager.
# COMMUNICATES WITH: EndBreakpoint.

class GoldenRunDriver(TargetDriver):
    def __init__(self, SCCDetector, functionsToSkip):
        super(GoldenRunDriver, self).__init__(SCCDetector, functionsToSkip)
        self.inFaultZone = False

    def proceedAndLog(self, goldenRunLog):
        steps = 0
        self.setInFaultZone(True)

        # Step until we reach an EndBreakpoint.
        print("Voy a entrar al while")
        while self.inFaultZone:
            selectedFrame = gdb.selected_frame()
            currentPC = selectedFrame.pc()
            functionName = selectedFrame.name()

            if (self.mustSkipFunction(functionName) or
                (self.SCCDetector and self.SCCDetector.isSCC(currentPC, functionName))):
                gdb.execute("finish")

            goldenRunLog.write("%#x\n" % gdb.selected_frame().pc())

            gdb.execute("stepi")
            steps += 1

        # Clear any remaining breakpoints.
        gdb.execute("delete")

        return steps

    # Set breakpoints at the target addresses given in the startPoints list,
    # then proceed until we hit one of said breakpoints.
    def advanceToStartPoint(self, startPoints):
        for startPoint in startPoints:
            gdb.Breakpoint(startPoint)

        gdb.execute("continue")
        gdb.execute("delete")

    def setEndBreakpoints(self, endPoints):
        for endPoint in endPoints:
            EndBreakpoint(endPoint, self)

    def setInFaultZone(self, inFaultZone):
        self.inFaultZone = inFaultZone

################################################################################

# A TargetDriver used for performing Injection Runs.
# USED BY: InjectionRunManager.
# USES: RobustoDetector.

class InjectionRunDriver(TargetDriver):
    def __init__(self, SCCDetector, functionsToSkip):
        super(InjectionRunDriver, self).__init__(SCCDetector, functionsToSkip)

    # Drive the target as many steps as specified by injectionStep.
    def goToInjectionStep(self, goldenRunLog, injectionStep, targetPC):
        ignoreCount = 0
        goldenRunLog.seek(0)

        # As we may hit the target PC before the injection step,
        # we'll ignore any appearances of the target PC in the log
        # before the injection step.
        if injectionStep > 0:
            # Make a copy of the file with every line preceding
            # the injection step line.
            slicedLog = itertools.islice(goldenRunLog, injectionStep)

            # Compute how many times we'll have to ignore the target PC
            # before reaching the injection point.
            for PCFromLog in slicedLog:
                if PCFromLog.rstrip() == targetPC:
                    ignoreCount += 1

            jeringa.log("Continues: %d" % ignoreCount)

            # Advance up to the injection step by setting a breakpoint
            # at the target PC with an ignore count equal to the amount
            # of times the target PC appears before the injection step.
            targetPCBreakpoint = gdb.Breakpoint("*%s" % targetPC, temporary=True)
            targetPCBreakpoint.ignore_count = ignoreCount
            gdb.execute("continue")

    # Proceed until the end of the program, checking the current PCs
    # against the Golden Run ones.
    def monitorAfterInjection(self, goldenRunLog, injectionStep):
        goldenRunLog.seek(0)
        error = False
        endOfFile = False
        PCFromLog = ""
        slicedLog = itertools.islice(goldenRunLog, injectionStep, None)

        while not error and not endOfFile:
            PCFromLog = next(slicedLog, "").rstrip()

            if PCFromLog:
                error = self.proceed(PCFromLog)
            else:
                endOfFile = True

        return error

    def proceed(self, PCFromLog):
        error = False
        skippedFunctionsError = False
        currentFunctionName = gdb.selected_frame().name()

        if currentFunctionName:
            skippedFunctionsError = self.skipThroughFunctions(currentFunctionName)

        currentPC = gdb.selected_frame().pc()

        if self.rightPC(currentPC, PCFromLog) and not skippedFunctionsError:
            gdb.execute("stepi")
        else:
            error = True
            self.printError(currentPC, PCFromLog)

        return error

    def skipThroughFunctions(self, functionName):
        currentPC = gdb.selected_frame().pc()
        SCCExecutionError = False

        if self.mustSkipFunction(functionName):
            gdb.execute("finish")
        elif self.SCCDetector and self.SCCDetector.isSCC(currentPC, functionName):
            SCCExecutionError = self.SCCDetector.executeSCC(currentPC, functionName)

        return SCCExecutionError

    def rightPC(self, currentPC, PCFromLog):
        return currentPC == long(PCFromLog, 0)

    def printError(self, currentPC, PCFromLog):
        jeringa.log("ERROR")
        jeringa.log("currentPC: %#x" % currentPC)
        jeringa.log("PCFromLog: %#x" % long(PCFromLog, 0))
